import { getQuickJS, QuickJSHandle, QuickJSVm } from 'quickjs-emscripten';
const QuickJSVmScopeSymbol = Symbol("QuickJSVmScope");

export function withScope<F extends (vm: QuickJSVm) => any>(vm: QuickJSVm, handle: F): {
  value: ReturnType<F>;
  dispose(): void;
} {
  let disposes: (() => void)[] = [];
  function wrap(handle: QuickJSHandle) {
    disposes.push(() => handle.alive && handle.dispose());
    return handle;
  }
  const isProxy = !!Reflect.get(vm, QuickJSVmScopeSymbol);
  function dispose() {
    if (isProxy) {
      // 避免多层代理
      Reflect.get(vm, QuickJSVmScopeSymbol)();
      return;
    }
    disposes.forEach((d) => d());
    // 手动释放闭包变量的内存
    disposes.length = 0;
  }
  const value = handle(
    isProxy
      ? vm
      : new Proxy(vm, {
        get(target: QuickJSVm, p: keyof QuickJSVm | typeof QuickJSVmScopeSymbol): any {
          if (p === QuickJSVmScopeSymbol) return dispose;
          const res = Reflect.get(target, p, target);
          if (p.startsWith("new") || ["getprop", "unwrapResult"].includes(p)) {
            return (...args: any[]): QuickJSHandle => {
              return wrap(Reflect.apply(res, target, args))
            }
          }
          if (["evalCode", "callFunction"].includes(p)) {
            return (...args: any[]) => {
              const res = (target[p] as any)(...args);
              disposes.push(() => {
                const handle = res.error ?? res.value;
                handle.alive && handle.dispose();
              })
              return res
            }
          }
          if (typeof res === "function") {
            return (...args: any[]) => {
              return Reflect.apply(res, target, args)
            }
          }
          return res;
        }
      })
  )
  return { value, dispose }
}


async function main() {
  const QuickJS = await getQuickJS();
  const vm = QuickJS.createVm();
  const world = vm.newString("world");
  vm.setProp(vm.global, "NAME", world);
  world.dispose();

  const result = vm.evalCode(`Hello ${NAME}@`);
  if (result.error) {
    console.log("Execution failed: ", vm.dump(result.error));
    result.error.dispose();
  } else {
    console.log("Success: ", vm.dump(result.value));
    result.value.dispose();
  }
  vm.dispose()
}

main();
